/*
 * \brief  Test for signalling framework
 * \author Norman Feske
 * \date   2008-09-06
 */

/*
 * Copyright (C) 2008-2009 Norman Feske
 * Genode Labs, Feske & Helmuth Systementwicklung GbR
 *
 * This file is part of the Genode OS framework, which is distributed
 * under the terms of the GNU General Public License version 2.
 */

#include <base/printf.h>
#include <base/signal.h>
#include <timer_session/connection.h>
#include <cap_session/connection.h>
#include <util/misc_math.h>

using namespace Genode;

/**
 * Transmit signals in a periodic fashion
 */
class Sender : Thread<4096>
{
	private:

		Signal_transmitter _transmitter;
		Timer::Connection  _timer;        /* timer connection for local use           */
		unsigned           _interval_ms;  /* interval between signals in milliseconds */
		bool               _stop;         /* state for destruction protocol           */
		unsigned           _submit_cnt;   /* statistics                               */
		bool               _idle;         /* suppress the submission of signals       */
		bool               _verbose;      /* print activities                         */

		/**
		 * Sender thread submits signals every '_interval_ms' milliseconds
		 */
		void entry()
		{
			while (!_stop) {

				if (!_idle) {
					_submit_cnt++;

					if (_verbose)
						printf("submit signal %d\n", _submit_cnt);

					_transmitter.submit();
				}

				if (_interval_ms)
					_timer.msleep(_interval_ms);
			}
		}

	public:

		/**
		 * Constructor
		 *
		 * \param receiver     signal destination
		 * \param interval_ms  interval between signals
		 * \param verbose      print status information
		 */
		Sender(Signal_receiver_capability receiver,
		       unsigned interval_ms, bool verbose = true)
		:
			_transmitter(receiver),
			_interval_ms(interval_ms),
			_stop(false),
			_submit_cnt(0),
			_idle(0),
			_verbose(verbose)
		{
			/* start thread at 'entry' function */
			start();
		}

		/**
		 * Destructor
		 */
		~Sender()
		{
			/* tell thread to stop iterating */
			_stop = true;

			/* wait for current 'msleep' call of the thread to finish */
			_timer.msleep(0);
		}

		/**
		 * Suppress the transmission of further signals
		 */
		void idle() { _idle = true; }

		/**
		 * Return total number of submitted notifications
		 */
		unsigned submit_cnt() { return _submit_cnt; }
};


/**
 * Signal handler receives signals and takes some time to handle each
 */
class Handler : Thread<4096>
{
	private:

		unsigned         _dispatch_ms;      /* time needed for dispatching a signal         */
		unsigned         _id;               /* unique ID of signal handler for debug output */
		static unsigned  _id_cnt;           /* counter for producing unique IDs             */
		Signal_receiver *_receiver;         /* signal endpoint                              */
		Timer::Connection _timer;           /* timer connection for local use               */
		bool              _stop;            /* state for destruction protocol               */
		unsigned          _receive_cnt;     /* number of received notifications             */
		unsigned          _activation_cnt;  /* number of invocations of the signal handler  */
		bool              _idle;            /* suppress the further handling of signals     */
		bool              _verbose;         /* print status information                     */

		/**
		 * Signal handler needs '_dispatch_ms' milliseconds for each signal
		 */
		void entry()
		{
			while (!_stop) {

				if (!_idle) {
					Signal signal = _receiver->wait_for_signal();

					if (_verbose)
						printf("handler %d got %d signal%s imprinted with %ld\n",
						       _id,
						       signal.num(),
						       signal.num() == 1 ? "" : "s",
						       signal.imprint());

					_receive_cnt += signal.num();
					_activation_cnt++;
				}

				if (_dispatch_ms)
					_timer.msleep(_dispatch_ms);
			}
		}

	public:

		/**
		 * Constructor
		 *
		 * \param receiver     receiver to request signals from
		 * \param dispatch_ms  duration of signal-handler activity
		 * \param verbose      print status information
		 */
		Handler(Signal_receiver *receiver, unsigned dispatch_ms, bool verbose = true)
		:
			_dispatch_ms(dispatch_ms),
			_id(++_id_cnt),
			_receiver(receiver),
			_stop(false),
			_receive_cnt(0),
			_activation_cnt(0),
			_idle(0),
			_verbose(verbose)
		{
			start();
		}

		/**
		 * Destructor
		 */
		~Handler()
		{
			/* tell thread to stop iterating */
			_stop = true;

			/* wait for current 'msleep' call of the thread to finish */
			_timer.msleep(0);
		}

		/**
		 * Suppress the handling of further signals
		 */
		void idle() { _idle = true; }

		/**
		 * Return total number of received notifications
		 */
		unsigned receive_cnt() { return _receive_cnt; }

		/**
		 * Return total number of signal-handler activations
		 */
		unsigned activation_cnt() { return _activation_cnt; }
};


/**
 * Counter for generating unique signal-handler IDs
 */
unsigned Handler::_id_cnt = 0;


/**
 * Counter for enumerating the tests
 */
static unsigned test_cnt = 0;


/**
 * Timer connection to be used by the main program
 */
static Timer::Connection timer;


/**
 * Connection to CAP service used for allocating signal-receiver capabilities
 */
static Genode::Cap_connection cap;


/**
 * Symbolic error codes
 */
class Test_failed { };
class Test_failed_with_unequal_sent_and_received_signals : public Test_failed { };
class Test_failed_with_unequal_activation_of_handlers    : public Test_failed { };


/**
 * Test for reliable notification delivery
 *
 * For this test, the produce more notification than that can be handled.
 * Still, the total number of notifications gets transmitted because of the
 * batching of notifications in one signal. This test fails if the number of
 * submitted notifications on the sender side does not match the number of
 * notifications received at the signal handler.
 */
void fast_sender_test()
{
	enum { SPEED            = 10 };
	enum { TEST_DURATION    = 50*SPEED };
	enum { HANDLER_INTERVAL = 10*SPEED };
	enum { SENDER_INTERVAL  = 2*SPEED };
	enum { FINISH_IDLE_TIME = 2*HANDLER_INTERVAL };

	printf("\n");
	printf("TEST %d: one sender, one handler, sender is faster than handler\n", ++test_cnt);
	printf("\n");

	Signal_receiver receiver(&cap);

	Handler *handler = new (env()->heap()) Handler(&receiver, HANDLER_INTERVAL, false);
	Sender  *sender  = new (env()->heap()) Sender(receiver.cap(123), SENDER_INTERVAL, false);

	timer.msleep(TEST_DURATION);

	/* stop emitting signals */
	printf("deactivate sender\n");
	sender->idle();
	timer.msleep(FINISH_IDLE_TIME);

	printf("\n");
	printf("sender submitted a total of %d signals\n", sender->submit_cnt());
	printf("handler received a total of %d signals\n", handler->receive_cnt());
	printf("\n");

	if (sender->submit_cnt() != handler->receive_cnt())
		throw Test_failed();

	destroy(env()->heap(), sender);
	destroy(env()->heap(), handler);

	printf("TEST %d FINISHED\n", test_cnt);
}


/**
 * Fairness test if multiple signal-handler threads are present at one receiver
 *
 * We expect that all handler threads get activated in a fair manner. The test
 * fails if the number of activations per handler differs by more than one.
 * Furthermore, if operating in non-descrete mode, the total number of sent and
 * handled notifications is checked.
 */
void multiple_handlers_test()
{
	enum { SPEED            = 10 };
	enum { TEST_DURATION    = 50*SPEED };
	enum { HANDLER_INTERVAL = 8*SPEED };
	enum { SENDER_INTERVAL  = 1*SPEED };
	enum { FINISH_IDLE_TIME = 2*HANDLER_INTERVAL };
	enum { NUM_HANDLERS     = 4 };

	printf("\n");
	printf("TEST %d: one busy sender, %d handlers\n",
	       ++test_cnt, NUM_HANDLERS);
	printf("\n");

	Signal_receiver receiver(&cap);
	Handler *handler[NUM_HANDLERS];
	for (int i = 0; i < NUM_HANDLERS; i++)
		handler[i] = new (env()->heap()) Handler(&receiver, HANDLER_INTERVAL);

	Sender *sender = new (env()->heap()) Sender(receiver.cap(123), SENDER_INTERVAL);

	timer.msleep(TEST_DURATION);

	/* stop emitting signals */
	printf("stop generating new notifications\n");
	sender->idle();
	timer.msleep(FINISH_IDLE_TIME);

	/* let handlers settle down */
	for (int i = 0; i < NUM_HANDLERS; i++)
		handler[i]->idle();
	timer.msleep(FINISH_IDLE_TIME);

	/* print signal delivery statistics */
	printf("\n");
	printf("sender submitted a total of %d signals\n",
	        sender->submit_cnt());
	unsigned total_receive_cnt = 0;
	for (int i = 0; i < NUM_HANDLERS; i++) {
		printf("handler %d received a total of %d signals\n",
		       i, handler[i]->receive_cnt());
		total_receive_cnt += handler[i]->receive_cnt();
	}
	printf("all handlers received a total of %d signals\n",
	       total_receive_cnt);

	/* check if number of sent notifications match the received ones */
	if (sender->submit_cnt() != total_receive_cnt)
		throw Test_failed_with_unequal_sent_and_received_signals();

	/* print activation statistics */
	printf("\n");
	for (int i = 0; i < NUM_HANDLERS; i++)
		printf("handler %d was activated %d times\n",
		       i, handler[i]->activation_cnt());
	printf("\n");

	/* check if handlers had been activated equally (tolerating a difference of one) */
	for (int i = 0; i < NUM_HANDLERS; i++) {

		int diff = handler[0]->activation_cnt()
		         - handler[(i + 1)/NUM_HANDLERS]->activation_cnt();

		if (abs(diff) > 1)
			throw Test_failed_with_unequal_activation_of_handlers();
	}

	/* cleanup */
	destroy(env()->heap(), sender);
	for (int i = 0; i < NUM_HANDLERS; i++)
		destroy(env()->heap(), handler[i]);

	printf("TEST %d FINISHED\n", test_cnt);
}


/**
 * Stress test to estimate signal throughput
 *
 * For this test, we disable status output and any simulated wait times.
 * We produce and handle notifications as fast as possible via spinning
 * loops at the sender and handler side.
 */
void stress_test()
{
	enum { SPEED            = 10 };
	enum { DURATION_SECONDS = 5  };
	enum { FINISH_IDLE_TIME = 100*SPEED };

	printf("\n");
	printf("TEST %d: stress test, busy signal transmission and handling\n", ++test_cnt);
	printf("\n");

	Signal_receiver receiver(&cap);

	Handler *handler = new (env()->heap()) Handler(&receiver, 0, false);
	Sender  *sender  = new (env()->heap()) Sender(receiver.cap(123), 0, false);

	for (int i = 1; i <= DURATION_SECONDS; i++) {
		printf("%d/%d\n", i, DURATION_SECONDS);
		timer.msleep(1000);
	}

	/* stop emitting signals */
	printf("deactivate sender\n");
	sender->idle();
	timer.msleep(FINISH_IDLE_TIME);

	printf("\n");
	printf("sender submitted a total of %d signals\n", sender->submit_cnt());
	printf("handler received a total of %d signals\n", handler->receive_cnt());
	printf("\n");
	printf("processed %d notifications per second\n",
	       (handler->receive_cnt())/DURATION_SECONDS);
	printf("handler was activated %d times per second\n",
	       (handler->activation_cnt())/DURATION_SECONDS);
	printf("\n");

	if (sender->submit_cnt() != handler->receive_cnt())
		throw Test_failed_with_unequal_sent_and_received_signals();

	destroy(env()->heap(), sender);
	destroy(env()->heap(), handler);

	printf("TEST %d FINISHED\n", test_cnt);
}


/**
 * Main program
 */
int main(int, char **)
{
	printf("--- signalling test ---\n");

	fast_sender_test();
	multiple_handlers_test();
	stress_test();

	printf("--- signalling test finished ---\n");
	return 0;
}
